// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.im.impl;

import net.x52im.mobileimsdk.server.protocal.Protocal;

import com.eva.framework.dbpool.DBDepend;
import com.eva.framework.dbpool.DBShell;
import com.eva.framework.utils.LoggerFactory;
import com.google.gson.Gson;
import com.x52im.rainbowchat.common.dto.cnst.UserProtocalsType;
import com.x52im.rainbowchat.im.dto.CMDBody4GroupNameChangedNotification;
import com.x52im.rainbowchat.im.dto.CMDBody4MyselfBeInvitedGroupResponse;
import com.x52im.rainbowchat.im.dto.MsgBody4Group;
import com.x52im.rainbowchat.im.dto.MsgBody4Guest;
import com.x52im.rainbowchat.im.dto.MsgBodyRoot;
import com.x52im.rainbowchat.im.util.MessageHelper;
import com.x52im.rainbowchat.push.ios.APNSPushHelper;
//import com.x52im.rainbowchat.http.logic.LogicProcessor2;

/**
 * 离线消息处理逻辑管理器。
 * <p>
 * 所有有关离线的处理逻辑都在本类中集中完成，方便统一管理。
 * 
 * <p>
 * <b>离线消息处理逻辑在什么地方实现合适？</b><br><br>
 * 详见“RainbowChat_pro_v4关键设计思路备忘.txt”。
 * 
 * @author Tony
 * @since 4.5
 */
public class OfflineMessageManager 
{
	private static DBShell db = new DBShell();
	
//	/**
//	 * 处理离线群聊指令/通知（用于群聊的指令和通知）.
//	 * 
//	 * @param p 原始Protocal协议包对象
//     * @param onlyDoAppPush true表示不做其它任何离线处理逻辑只做App的Push通知，否则正常走离线处理完整逻辑（包括App的
//     * Push通知），正常情况本参数设为false即可。为true时，用于桥接通信成功，但手机端确实不在本App Server上，那么此时
//     * 做一个only push（不需要离线存储逻辑，否则web那端再搞离线存储那消息就重复了），是合理的，这就是App这端的特殊情况！
//     * 
//	 * @return
//	 * @see Protocal
//	 */
//	public static boolean processOfflineGroupSysCMD(Protocal p, boolean onlyDoAppPush)
//	{
//		// 消息的接收者
//		String userId = p.getTo();
//		// 消息的发出者
//		String from_user_id = "0";
//		String fingerPrint = p.getFp();
//		// 本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不应为空，表示本条群聊消息发于在哪个群里
//		String group_id = null;
//		
//		String msg = null;
//		
//		// 按指含类型解析协议包并处理离线逻辑
//		// 补充：指令的解析请与 com.x52im.rainbowchat.im.util.MessageHelper中的对应指令组织和发送代码保持一致！
//		switch(p.getTypeu())
//		{
//			case UserProtocalsType.MT46_OF_GROUP$SYSCMD_MYSELF$BE$INVITE_FROM$SERVER:	
//			{
//				CMDBody4MyselfBeInvitedGroupResponse cmdBody = new Gson().fromJson(
//						p.getDataContent(), CMDBody4MyselfBeInvitedGroupResponse.class);
//				
//				msg = "\""+cmdBody.getInitveBeNickName()+"\"邀请你加入了群聊";
//				group_id = cmdBody.getG_id();
//				
//				break;
//			}
//			case UserProtocalsType.MT47_OF_GROUP$SYSCMD_COMMON$INFO_FROM$SERVER:
//			case UserProtocalsType.MT48_OF_GROUP$SYSCMD_DISMISSED_FROM$SERVER:
//			case UserProtocalsType.MT49_OF_GROUP$SYSCMD_YOU$BE$KICKOUT_FROM$SERVER:
//			case UserProtocalsType.MT50_OF_GROUP$SYSCMD_SOMEONEB$REMOVED_FROM$SERVER:
//			{
//				MsgBody4Group body = new Gson().fromJson(p.getDataContent(), MsgBody4Group.class);
//				msg = body.getM();
//				group_id = body.getT();
//				break;
//			}
//			case UserProtocalsType.MT51_OF_GROUP$SYSCMD_GROUP$NAME$CHANGED_FROM$SERVER:	
//			{
//				CMDBody4GroupNameChangedNotification cmdBody = 
//						new Gson().fromJson(p.getDataContent(), CMDBody4GroupNameChangedNotification.class);
//				msg = cmdBody.getNotificationContent();
//				group_id = cmdBody.getGid();
//				break;
//			}
//		}
//		
//		if(onlyDoAppPush)
//		{
//			try 
//			{
//				//## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
//				APNSPushHelper.tryPushChattingMessage(chatType, msgType, content, fromUid, toUid, toGid);
////				APNSPushHelper.tryPushGroupCMD(userId, group_id, msg);
//				return true;
//			} 
//			catch (Exception e)
//			{
//				LoggerFactory.getLog().warn(e.getMessage(), e);
//				return false;
//			}
//		}
//		else
//		{
//			LoggerFactory.getLog().debug("[RBChat]【离线存储】【群聊CMD】[gid="+group_id
//					+"]正在处理："+from_user_id+"发给"+userId+"的离线系统通知：str="+msg
//					+",fingerPring="+fingerPrint+"......");
//			try
//			{
//				if(userId != null && from_user_id != null && msg != null && group_id != null)
//				{
//					//## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
//					APNSPushHelper.tryPushChattingMessage(chatType, msgType, content, fromUid, toUid, toGid);
////					APNSPushHelper.tryPushGroupCMD(userId, group_id, msg);
//
//					//## 将该条群聊系统通知插入到数据库中（以备接收者上线后拿到）
//					insertOffline(from_user_id
//							, userId
//							, ""+MsgBodyRoot.TYPE_SYSTEAM$INFO
//							, msg
//							, fingerPrint
//							, ""+System.currentTimeMillis()
//							, MsgBodyRoot.CHAT_TYPE_GROUP$CHAT
//							, group_id);
//
//					return true;
//				}
//				else
//				{
//					LoggerFactory.getLog().error("[RBChat]【群聊CMD】[gid="+group_id
//							+"]离线群通知数据有误，该条离线系统通知无法存储到DB中：userId="
//							+userId+", from_user_id="+from_user_id+", msg="+msg+")");
//				}
//			}
//			catch (Exception e)
//			{
//				LoggerFactory.getLog().warn("[RBChat]【离线存储】【群聊CMD】[gid="+group_id
//						+"]处理离线存储系统通知出错了 -> "+from_user_id+"发给"+userId
//						+"的离线系统通知：str="+msg, e);
//			}
//			
//			return false;
//		}
//	}
	
	/**
	 * 处理离线聊天消息（专用于普通聊天消息(含一对一、群聊））
	 * 
	 * @param p 原始Protocal协议包对象
	 * @param onlyDoAppPush true表示不做其它任何离线处理逻辑只做App的Push通知，否则正常走离线处理完整逻辑（包括App的
     * Push通知），正常情况本参数设为false即可。为true时，用于桥接通信成功，但手机端确实不在本App Server上，那么此时
     * 做一个only push（不需要离线存储逻辑，否则web那端再搞离线存储那消息就重复了），是合理的，这就是App这端的特殊情况！
	 * @return
	 */
	public static boolean processOfflineMessage(Protocal p, boolean onlyDoAppPush)
	{
		// 接收者uid
		String userId = null;
		// 发送者uid
		String from_user_id = null;
		// 消息或指令内容（一般而言，这会是个JSON字符串，因为是个复合对象）
		String dataContent = p.getDataContent();
		// 消息或指令指纹码（即唯一ID）
		String fingerPrint = p.getFp();
		
		// 用于离线存储时的聊天模式类型（见 MsgBodyRoot中的常量定义 ）
		int chatType = -1;
		// 用于离线存储时的消息内容类型（见 MsgBodyRoot中的常量定义 ）
		int msgType = -1;
		
		// 本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不应为空，表示本条群聊消息发于在哪个群里
		String group_id = null;
		// 本字段只有在聊天模式类型chatType为“2 群聊”时有意义，表示群聊消息扩散写前原始消息的指纹码（目前主要用于消息“撤回”功能时）
		String parent_fp = null;
		
		String msg = null;
		
		switch(p.getTypeu())
		{
			// 一对一好友聊天消息用于离线处理的协议解析逻辑（C2C方向）
			case UserProtocalsType.MT03_OF_CHATTING_MESSAGE:
			{
				MsgBodyRoot msgBody = MsgBodyRoot.parseFromSender(dataContent);
				
				userId = p.getTo();
				from_user_id = p.getFrom();
				chatType = msgBody.getCy();//MsgBodyRoot.CHAT_TYPE_FREIDN$CHAT;
				
				group_id = null;
				parent_fp = null;
				msg = msgBody.getM();
				msgType = msgBody.getTy();
				
				break;
			}
			// 一对一陌生人聊天消息用于离线处理的协议解析逻辑（S2C方向）
			case UserProtocalsType.MT43_OF_TEMP$CHAT$MSG_SERVER$TO$B:
			{
				// 解析临时聊天消息体内容(dataContent直接传的是Protocal的content字段哦，也就是MsgBody4Guest的JSON串！)
				MsgBody4Guest msgBody = MessageHelper.pareseTempChatMsg_A$TO$SERVER_Message(dataContent);
				
				userId = msgBody.getT();
				from_user_id = msgBody.getF();
				chatType = msgBody.getCy();
				
				group_id = null;
				parent_fp = null;
				msg = msgBody.getM();
				msgType = msgBody.getTy();
				
				break;
			}
			// 一对对群聊天消息用于离线处理的协议解析逻辑（S2C方向）
			case UserProtocalsType.MT45_OF_GROUP$CHAT$MSG_SERVER$TO$B:	
			{
				// 解析群组聊天消息体内容(dataContent直接传的是Protocal的content字段哦，也就是MsgBody4Guest的JSON串！)
				MsgBody4Group msgBody = MessageHelper.pareseGroupChatMsg_A$TO$SERVER_Message(dataContent);
				
				userId = p.getTo();// 本条扩散写消息的接收人
				from_user_id = msgBody.getF();
				chatType = msgBody.getCy();
				
				group_id = msgBody.getT(); // 群聊时，协议体msgBody中的t字段存放的就是群id（这是在im协议里约定好的，详见IM协议手册）
				parent_fp = msgBody.getParentFp();
				msg = msgBody.getM();
				msgType = msgBody.getTy();
				
				break;
			}
			
			/******************************* 以下指令类型属于群聊系统通知 START *************************************/
			case UserProtocalsType.MT46_OF_GROUP$SYSCMD_MYSELF$BE$INVITE_FROM$SERVER:	
			{
				CMDBody4MyselfBeInvitedGroupResponse cmdBody = new Gson().fromJson(
						p.getDataContent(), CMDBody4MyselfBeInvitedGroupResponse.class);
				
				userId = p.getTo();// 本条扩散写消息的接收人
				from_user_id = "0";// 消息的发起人（这是“服务端”发出的，所以是“0”）
				chatType = MsgBodyRoot.CHAT_TYPE_GROUP$CHAT;
				
				group_id = cmdBody.getG_id();
				parent_fp = null;
				msg = "\""+cmdBody.getInitveBeNickName()+"\"邀请你加入了群聊";
				msgType = MsgBodyRoot.TYPE_SYSTEAM$INFO;
				
				break;
			}
			case UserProtocalsType.MT47_OF_GROUP$SYSCMD_COMMON$INFO_FROM$SERVER:
			case UserProtocalsType.MT48_OF_GROUP$SYSCMD_DISMISSED_FROM$SERVER:
			case UserProtocalsType.MT49_OF_GROUP$SYSCMD_YOU$BE$KICKOUT_FROM$SERVER:
			case UserProtocalsType.MT50_OF_GROUP$SYSCMD_SOMEONEB$REMOVED_FROM$SERVER:
			{
				MsgBody4Group body = new Gson().fromJson(p.getDataContent(), MsgBody4Group.class);
				
				userId = p.getTo();// 本条扩散写消息的接收人
				from_user_id = "0";// 消息的发起人（这是“服务端”发出的，所以是“0”）
				chatType = MsgBodyRoot.CHAT_TYPE_GROUP$CHAT;
				
				group_id = body.getT();
				parent_fp = null;
				msg = body.getM();
				msgType = MsgBodyRoot.TYPE_SYSTEAM$INFO;
				
				break;
			}
			case UserProtocalsType.MT51_OF_GROUP$SYSCMD_GROUP$NAME$CHANGED_FROM$SERVER:	
			{
				CMDBody4GroupNameChangedNotification cmdBody = new Gson().fromJson(p.getDataContent(), CMDBody4GroupNameChangedNotification.class);
				
				userId = p.getTo();// 本条扩散写消息的接收人
				from_user_id = "0";// 消息的发起人（这是“服务端”发出的，所以是“0”）
				chatType = MsgBodyRoot.CHAT_TYPE_GROUP$CHAT;
				
				group_id = cmdBody.getGid();
				parent_fp = null;
				msg = cmdBody.getNotificationContent();
				msgType = MsgBodyRoot.TYPE_SYSTEAM$INFO;
				
				break;
			}
			/******************************* 以下指令类型属于群聊系统通知 END *************************************/
		}
		
		if(onlyDoAppPush)
		{
			try 
			{
//				MsgBodyRoot tm = MsgBodyRoot.parseFromSender(dataContent);
				
				//## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
				APNSPushHelper.tryPushChattingMessage(chatType, msgType, msg, from_user_id, userId, group_id);
				
				return true;
			} 
			catch (Exception e)
			{
				LoggerFactory.getLog().warn(e.getMessage(), e);
				return false;
			}
		}
		else
		{
			LoggerFactory.getLog().debug("[RBChat]【离线存储】[chatType="+chatType+",msgType="+msgType+",gid="+group_id
					+"]正在处理："+from_user_id+"发给"+userId+"的离线消息：str="+dataContent+",fingerPring="+fingerPrint+",parent_fp="+parent_fp+"......");
			try
			{
//				MsgBodyRoot tm = MsgBodyRoot.parseFromSender(dataContent);
	
//				if(tm != null && tm.getF() != null && tm.getT() != null)
				if(msg != null && from_user_id != null && userId != null)
				{
					if(fingerPrint != null && db.queryData("select 1 from missu_offline_history where " +
									"	history_content2 is not null and history_content2='"+fingerPrint+"'").size()>0)
					{
						LoggerFactory.getLog().debug("[RBChat]【离线存储】[chatType="+chatType+",msgType="+msgType+",gid="+group_id
								+"]"+from_user_id+"发给"+userId+"的离线消息：str="+dataContent
								+",fingerPring="+fingerPrint+",parent_fp="+parent_fp+" 已经被放入DB中存储了，本条可能是客户" +"端QoS机制重复发过来的，将被忽略(无需存储).");
					}
					else
					{
						//## 尝试向ios设备进行APNs离线消息推送（推送是基于netty+HTTP/2实现，因而是异步的不会阻塞余下代码执行）
						APNSPushHelper.tryPushChattingMessage(chatType, msgType, msg, from_user_id, userId, group_id);
						
						// 将该条聊天消息插入到数据库中（以备下次接收方B上线后拿到）
						insertOffline(from_user_id
								, userId
								, String.valueOf(msgType)
								, msg
								, fingerPrint
								, ""+System.currentTimeMillis()
								, chatType
								, group_id
								, parent_fp);
					}
				}
				else
				{
					LoggerFactory.getLog().error("[RBChat]【离线存储】[chatType="+chatType+",msgType="+msgType+",gid="+group_id
							+"]离线消息数据有误，该条离线消息无法存储到DB中：("
							+from_user_id+"发给"+userId+"的离线消息：str="+dataContent+",fingerPring="+fingerPrint+",parent_fp="+parent_fp+")");
				}
				return true;
			}
			catch (Exception e)
			{
				LoggerFactory.getLog().debug("[RBChat]【离线存储】[chatType="+chatType+",msgType="+msgType+",gid="+group_id
						+"]处理离线存储消息时出错了 -> "+from_user_id+"发给"+userId+"的离线消息：str="+dataContent+",fingerPring="+fingerPrint+",parent_fp="+parent_fp, e);
			}
			
			return false;
		}
	}
	
	/**
	 * 插入一条离线消息（包括文字、图片消息、语音留言消息、各种指令等）到数据库.
	 * 
	 * @param toUid 消息的接收者
	 * @param fromUid 消息的发出者
	 * @param msgType 聊天消息类型，详见{@link MsgBodyRoot}
	 * @param message 聊天消息内容
	 * @param messageContent2 本字段用于存放fingerPrint指纹码
	 * @param chatType 聊天模式类型，详见 {@link MsgBodyRoot}
	 * @param group_id 本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不应为空，表示本条群聊消息发于在哪个群里
	 * @param parent_fp 本字段只有在聊天模式类型chatType为“2 群聊”时有意义，表示群聊消息扩散写前原始消息的指纹码（目前主要用于消息“撤回”功能时）
	 * @throws Exception e
	 */
	private static void insertOffline(String fromUid, String toUid
			, String msgType, String message, String messageContent2, String history_time2
			, int chatType, String group_id, String parent_fp) throws Exception
	{
		// 将该条临时聊天消息插入到数据库中（以备下次接收方B上线后拿到）
		db.update(
				"INSERT INTO missu_offline_history(user_uid,friend_user_uid, chat_type,history_type" +
						",history_content,history_content2,history_time2,history_time,group_id,parent_fp)" +
						" VALUES(?,?,?,?,?,?,?,"+DBDepend.getDefaultDatetimeFunc()+",?,?)"
				, new Object[]{fromUid
						, toUid
						, chatType
						, msgType
						, message
						, messageContent2
						, history_time2
						, group_id // 本字段只有在聊天模式类型chatType为“2 群聊”时有意义且不为空。表示本条群聊消息发于在哪个群里
						, parent_fp
				}
				, false);
	}
}
